package com.opdar.platform.core.base;

import com.opdar.platform.core.convert.*;
import org.apache.commons.fileupload.FileItem;
import org.springframework.cglib.core.ReflectUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.sql.Timestamp;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class ParameterConvertManager {

    private static Map<Class<?>, Class<? extends ParameterConvert>> converts = new HashMap<Class<?>, Class<? extends ParameterConvert>>() {
        {
            put(Integer.class, IntegerConvert.class);
            put(Integer[].class, IntegerConvert.class);
            put(int.class, IntegerConvert.class);
            put(int[].class, IntegerConvert.class);
            put(Long.class, LongConvert.class);
            put(Long[].class, LongConvert.class);
            put(long.class, LongConvert.class);
            put(long[].class, LongConvert.class);
            put(String.class, StringConvert.class);
            put(String[].class, StringConvert.class);
            put(Date.class, DateConvert.class);
            put(boolean.class, BoolConvert.class);
            put(Boolean.class, BoolConvert.class);
            put(Timestamp.class, TimestampConvert.class);
            put(FileItem.class,PrototypeConvert.class);
            put(FileItem[].class,PrototypeConvert.class);
        }
    };

    public static <T>void addParameterConvert(Class<T> clz,Class<? extends ParameterConvert<T>> convert){
        converts.put(clz,convert);
    }

    public static ParameterConvert<?> getParameterConvert(Type type) {
        ParameterConvert<?> convert = null;
        if (type instanceof ParameterizedType) {
            Type rawType = ((ParameterizedType) type).getRawType();
            if (List.class.isAssignableFrom((Class<?>) rawType)) {
                Type actualType = ((ParameterizedType) type).getActualTypeArguments()[0];
                if (actualType instanceof Class) {
                    ParameterConvert<?> actualTypeConvert = ParameterConvertManager.getParameterValConvert((Class<?>) actualType);
                    ListConvertInvocationHandler handler = new ListConvertInvocationHandler(actualTypeConvert);
                    convert = (ParameterConvert<?>) Proxy.newProxyInstance(ParameterConvert.class.getClassLoader(),
                            new Class<?>[]{ParameterConvert.class}, handler);
                }
            }
        } else if (type instanceof Class) {
            convert = ParameterConvertManager.getParameterValConvert((Class<?>) type);
        }
        return convert;
    }

    private static ParameterConvert<?> getParameterValConvert(Class<?> type) {
        ParameterConvert<?> convert = null;
        if(type.isEnum()){
            convert = new EnumConvert(type);
        }else if (ParameterConvertManager.containsKey(type)) {
            Class<? extends ParameterConvert> convertClz = ParameterConvertManager.get(type);
            convert = (ParameterConvert<?>) ReflectUtils.newInstance(convertClz);
        } else {
            Field[] fields = type.getDeclaredFields();
            Map<String, ParameterConvert> convertMap = new HashMap<String, ParameterConvert>();
            for (Field field : fields) {
                Class<?> fieldType = field.getType();
                if(ParameterConvertManager.containsKey(fieldType)){
                    Class<? extends ParameterConvert> convertClz = ParameterConvertManager.get(fieldType);
                    ParameterConvert<?> c = (ParameterConvert<?>) ReflectUtils.newInstance(convertClz);
                    if (c != null) {
                        convertMap.put(field.getName().toLowerCase(), c);
                    }
                }
            }
            ObjectConvertInvocationHandler handler = new ObjectConvertInvocationHandler(type, convertMap);
            convert = (ObjectConvert) Proxy.newProxyInstance(ObjectConvert.class.getClassLoader(),
                    new Class<?>[]{ObjectConvert.class}, handler);
        }
        return convert;
    }

    public static boolean containsKey(Object key) {
        return converts.containsKey(key);
    }

    public static Class<? extends ParameterConvert> get(Object key) {
        return converts.get(key);
    }
}
